from typing import Dict, Any
import json
import httpx
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models import LLMModel, LLMProvider, LLMSecretVault
from app.config import settings
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import hashlib
import os
import logging


async def call_llm(session: AsyncSession, model_id: str, messages: list[Dict[str, Any]]) -> Dict[str, Any]:
    m = await session.get(LLMModel, model_id)
    if not m:
        raise ValueError("model_not_found")
    p = await session.get(LLMProvider, m.provider_id)
    if not p or not p.enabled:
        raise ValueError("provider_not_available")
    secret = await session.execute(select(LLMSecretVault).where(LLMSecretVault.provider_id == p.id, LLMSecretVault.enabled == True))
    sv = secret.scalars().first()
    if not sv:
        raise ValueError("secret_not_configured")
    def _decrypt_token(vault: LLMSecretVault) -> str:
        algo = (vault.cipher_algo or '').lower()
        key_src = settings.LLM_SECRET_KEY or settings.JWT_SECRET
        if not key_src:
            logging.warning("[LLM] no key source configured; fallback to raw decode")
            return vault.cipher_text.decode('utf-8', errors='ignore')
        try:
            if algo == 'aes-256-gcm':
                key = hashlib.sha256(key_src.encode()).digest()
                aes = AESGCM(key)
                iv = vault.iv or b''
                data = (vault.cipher_text or b'') + (vault.auth_tag or b'')
                plain = aes.decrypt(iv, data, None)
                return plain.decode('utf-8')
            return vault.cipher_text.decode('utf-8', errors='ignore')
        except Exception as ex:
            logging.error("[LLM] decrypt_token_error=%s", str(ex))
            return vault.cipher_text.decode('utf-8', errors='ignore')

    token = _decrypt_token(sv)
    base_cfg = (p.base_url or "").rstrip('/')
    path_cfg = (m.endpoint_path or "").strip()
    if p.code == "aliyun_bailian":
        base = "https://dashscope.aliyuncs.com"
        path = "/compatible-mode/v1/chat/completions"
    else:
        base = base_cfg or "https://api.openai.com"
        path = path_cfg or "/v1/chat/completions"
    if not path.startswith("/"): path = "/" + path
    url = base + path
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    if m.extra_headers_json:
        try:
            extra = json.loads(m.extra_headers_json)
            if isinstance(extra, dict):
                headers.update({str(k): str(v) for k, v in extra.items()})
        except Exception:
            pass
    payload = {"model": m.code, "messages": messages}
    logging.info("[LLM] provider=%s model=%s url=%s", p.code, m.code, url)
    logging.info("[LLM] request.headers=%s", headers)
    logging.info("[LLM] request.payload=%s", json.dumps(payload, ensure_ascii=False))
    try:
        async with httpx.AsyncClient(timeout=30.0) as client:
            r = await client.post(url, headers=headers, json=payload)
    except httpx.RequestError as ex:
        logging.error("[LLM] request_error=%s", str(ex))
        raise
    try:
        r.raise_for_status()
    except httpx.HTTPStatusError as ex:
        logging.error("[LLM] response_status=%s body=%s", ex.response.status_code, ex.response.text)
        raise
    logging.info("[LLM] response_status=%s", r.status_code)
    logging.debug("[LLM] response_body=%s", r.text)
    data = r.json()
    text = None
    if isinstance(data, dict):
        c = data.get("choices")
        if isinstance(c, list) and c:
            msg = c[0].get("message") or {}
            text = msg.get("content")
    if not text:
        text = json.dumps(data, ensure_ascii=False)
    return {"text": text, "raw": data}


def generate_section_text(model: str, variables: Dict[str, Any]) -> str:
    title = variables.get("title", "章节")
    activity_name = variables.get("activity_name", "活动")
    score_total = variables.get("score_total", "-")
    return f"【{title}】\n活动：{activity_name}；综合得分：{score_total}。根据提供的数据，撰写专业、客观的评估描述（占位）。"
